package cn.lg.soar.core.manager;

import cn.lg.soar.common.util.PathMatcher;
import cn.lg.soar.common.util.hardware.SystemUtils;
import cn.lg.soar.core.dto.PermitLevel;
import cn.lg.soar.core.enums.PermitLevelEnum;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.*;
import java.util.function.Function;

/**
 * 权限级别管理器
 * @author luguoxiang
 * @date 2022/4/4
 * 开源项目：https://gitee.com/lgx1992/lg-soar 求star！请给我star！请帮我点个star！
 */
public class PermitLevelManager {

    protected static final Logger LOGGER = LoggerFactory.getLogger(PermitLevelManager.class);

    /**
     * 缓存权限级别匹配结果
     */
    private final Cache<String, PermitLevel> cache;

    /**
     * 默认级别
     */
    private final PermitLevel defLevel;

    private String[] closedPermitLevels = new String[0];;
    private String[] authorizedPermitLevels = new String[0];;
    private Map.Entry<String, PermitLevel>[] userTypePermitLevels = new Map.Entry[0];
    private String[] authenticatedPermitLevels = new String[0];;
    private String[] identifiablePermitLevels = new String[0];;
    private String[] anonPermitLevels = new String[0];;


    public PermitLevelManager(PermitLevel defLevel, Cache<String, PermitLevel> cache) {
        this.defLevel = defLevel;
        this.cache = cache;
    }

    public PermitLevelManager(PermitLevel defLevel) {
        this.defLevel = defLevel;
        long cacheSize = SystemUtils.getTotalMemory() >>> 17;

        cache = Caffeine.newBuilder()
                .maximumSize(cacheSize)
                .initialCapacity(64)
                .expireAfterAccess(Duration.ofMinutes(5))
                .build();
        LOGGER.info("permit-level cache max size {}", cacheSize);
    }

    public void setLevelPatterns(PermitLevelEnum level, Collection<String> patterns) {
        switch (level) {
            case closed -> closedPermitLevels = patterns.toArray(new String[0]);
            case authorized -> authorizedPermitLevels = patterns.toArray(new String[0]);
            case user_type -> throw new RuntimeException("不支持的配置");
            case authenticated -> authenticatedPermitLevels = patterns.toArray(new String[0]);
            case identifiable -> identifiablePermitLevels = patterns.toArray(new String[0]);
            case anon -> anonPermitLevels = patterns.toArray(new String[0]);
        }
    }

    public void setPermitUserTypes(Map<Integer, Set<String>> map) {
        Map<String, Set<Integer>> map1 = new HashMap<>(256);
        map.forEach((userType, patterns) -> {
            for (String pattern : patterns) {
                map1.computeIfAbsent(pattern, k -> new HashSet<>()).add(userType);
            }
        });

        List<PermitLevel> permitLevels = new ArrayList<>(map.size());
        Function<Set<Integer>, PermitLevel> getLevel = set -> {
            for (PermitLevel permitLevel : permitLevels) {
                if (permitLevel.getUserTypes().equals(set)) {
                    return permitLevel;
                }
            }
            return PermitLevel.valueOf(set);
        };
        Map<String, PermitLevel> map2 = new HashMap<>(map1.size());
        map1.forEach((key, value) -> {
            map2.put(key, getLevel.apply(value));
        });
        permitLevels.clear();
        map1.clear();
        userTypePermitLevels = map2.entrySet().toArray(new Map.Entry[0]);
        map2.clear();
    }

    /**
     * 匹配权限级别
     * @param permit
     * @return
     */
    public PermitLevel match(String permit) {
        // 小于250的路径才缓存，以免内存过大
        if (permit.length() < 256) {
            // 通过缓存匹配
            return cache.get(permit, this::innerMatch);
        }
        // 直接匹配
        return innerMatch(permit);
    }

    /**
     * 匹配权限级别
     * @param permit
     * @return
     */
    private PermitLevel innerMatch(String permit) {
        // 优先级由高到低
        if (PathMatcher.matchAny(permit, closedPermitLevels)) {
            return PermitLevel.closed;
        }
        if (PathMatcher.matchAny(permit, authorizedPermitLevels)) {
            return PermitLevel.authorized;
        }
        for (Map.Entry<String, PermitLevel> entry : userTypePermitLevels) {
            String key = entry.getKey();
            if (PathMatcher.match(permit, key)) {
                return entry.getValue();
            }
        }
        if (PathMatcher.matchAny(permit, authenticatedPermitLevels)) {
            return PermitLevel.authenticated;
        }
        if (PathMatcher.matchAny(permit, identifiablePermitLevels)) {
            return PermitLevel.identifiable;
        }
        if (PathMatcher.matchAny(permit, anonPermitLevels)) {
            return PermitLevel.anon;
        }
        return defLevel;
    }

}
